package com.panopset.refactorsf;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

import com.panopset.java.Version;
import com.panopset.sf.ComponentFactory;
import com.panopset.sf.FontManager;
import com.panopset.sf.Windoc;

/**
 *
 * Drop down selection.
 *
 * @param <T>
 *            Any serializable object, make sure you override toString because
 *            that is where the menu item for this object will get it's text.
 *
 * @author Karl Dinwiddie
 *
 */
public abstract class Dropper<T> extends PanField<TPanel> {
	
	
	protected String getGuiValue() {
		return getSelectionLbl().getText();
	}
	
	protected void setGuiValue(final String value) {
		getSelectionLbl().setText(value);
	}

    /**
     * @param title
     *            Title.
     * @param defaultValue
     *            Default value.
     * @param tip
     *            Tip.
     */
    public Dropper(final Windoc wd, final String title,
            final String defaultValue, final String tip,
            final String key) {
        super(wd, title, tip, defaultValue, key);
    }


    /**
     * @param title
     *            Title.
     * @param defaultValue
     *            Default value.
     * @param tip
     *            Tip.
     */
    public Dropper(final Windoc wd, final String title,
            final String defaultValue, final String tip) {
        this(wd, title, tip, defaultValue, "");
    }


    /**
     * Test point is 100, 100.
     */
    private static final Point TEST_POINT = new Point(100, 100);

    /**
     * Test dimension is 600 x 300.
     */
    private static final Dimension TEST_SIZE = new Dimension(600, 300);

    /**
     * Entry point.
     *
     * @param args
     *            test, no args.
     */
    public static void main(final String... args) {
        JFrame f = new JFrame("testing...");
        f.setLocation(TEST_POINT);
        f.setSize(TEST_SIZE);
        Dropper<JLabel> d = new Dropper<JLabel>(null, "border title", "b",
                "dropper tip", "drptest") {

            @Override
            public String getMenuName(final JLabel key, final int count) {
                return "";
            }

            @Override
            public Map<String, JLabel> createSelectionList() {
                final Map<String, JLabel> rtn = new HashMap<String, JLabel>();
                JLabel lbl = new JLabel("albl") {

                    @Override
                    public String toString() {
                        return "astr";
                    }

                    /**
                     * Serial version id.
                     */
                    private static final long serialVersionUID = Version.UNI_VERSION;
                };
                lbl.setToolTipText("attt");
                rtn.put("a", lbl);
                rtn.put("b", lbl);
                return rtn;
            }

            @Override
            public String getSelectionTipText(final JLabel selection) {
                return selection.getToolTipText();
            }

            @Override
            public String getTopMenuName() {
                return "zongo the top menu";
            }

            @Override
            public String getTopMenuTipText(final JLabel sample) {
                return "bongo the top level tip text";
            }

        };
        f.add(d.getPanel());
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
    }

    /**
     * @return Top menu name.
     */
    public abstract String getTopMenuName();

    /**
     * If a key is to appear in a sub-menu, use the key and the count to specify
     * the name of the sub-menu.
     *
     * @param val
     *            Menu item storage.
     * @param count
     *            Current count.
     * @return If blank is returned, the key will be added to the top menu.
     */
    public abstract String getMenuName(T val, int count);

    /**
     * @return Selection list.
     */
    public abstract Map<String, T> createSelectionList();

    /**
     * Map.
     */
    private Map<String, T> map;

    /**
     * @return Selection list.
     */
    public final Map<String, T> getSelectionList() {
        if (map == null) {
            map = createSelectionList();
        }
        return map;
    }

    /**
     * @param selection
     *            Selection.
     * @return Selection tool tip text.
     */
    public abstract String getSelectionTipText(T selection);

    /**
     * Get top level menu tip text for a dropper item.
     *
     * @param sample
     *            Item in top level menu.
     * @return Tool tip text for the top level menu of the given item.
     */
    public abstract String getTopMenuTipText(T sample);

    /**
     * Action listener.
     */
    private ActionListener listener;

    /**
     * Menu bar.
     */
    private JMenuBar menuBar;

    @Override
    public final TPanel createComponent() {
        TPanel p = new TPanel();
        p.add(getSelectorPnl());
        p.add(getSelectionPnl());
        return p;
    }

    /**
     * Alert new selection.
     *
     * @param o
     *            Newly selected item.
     */
    protected void alertNewSelection(final T o) {

    }

    /**
     * @return Action listener.
     */
    private ActionListener getListener() {
        if (this.listener == null) {
            this.listener = new ActionListener() {

                @SuppressWarnings("unchecked")
                @Override
                public void actionPerformed(final ActionEvent e) {
                    if (e.getSource() instanceof StorageMenuItem) {
                        StorageMenuItem<T> item = (StorageMenuItem<T>) e
                                .getSource();
                        getSelectionLbl().setText(item.getText());
                        alertNewSelection(item.getItemStorage());
                    }
                }

            };
        }
        return this.listener;
    }

    /**
     * @return Menu bar.
     */
    private JMenuBar getMenuBar() {
        if (this.menuBar == null) {
            this.menuBar = new JMenuBar();
        }
        return this.menuBar;
    }

    /**
     * Refresh.
     */
    public final void refresh() {
        getMenuBar().removeAll();
        FontManager fm = FontManager.getInstance();
        Map<String, Stack<StorageMenuItem<T>>> menus = new HashMap<String, Stack<StorageMenuItem<T>>>();
        int count = 0;
        for (String key : getSelectionList().keySet()) {
            final T val = getSelectionList().get(key);
            String menuName = getMenuName(val, count++);
            Stack<StorageMenuItem<T>> menuStack = menus.get(menuName);
            if (menuStack == null) {
                menuStack = new Stack<StorageMenuItem<T>>();
                menus.put(menuName, menuStack);
            }
            StorageMenuItem<T> mi = new StorageMenuItem<T>(val);
            fm.register(mi, FontManager.FontType.monospace);
            mi.setToolTipText(getSelectionTipText(getSelectionList().get(key)));
            mi.addActionListener(getListener());
            menuStack.push(mi);
        }
        JMenu valueMenu = new JMenu(getTopMenuName());
        fm.register(valueMenu, FontManager.FontType.monospace);
        getMenuBar().add(valueMenu);
        if (menus.size() >= 1) {
            if (menus.size() == 1) {
                for (Entry<String, Stack<StorageMenuItem<T>>> entry : menus
                        .entrySet()) {
                    sortAndPopulate(valueMenu, entry.getValue());
                }
            } else {
                ArrayList<ComparableMenu> al = new ArrayList<ComparableMenu>();
                for (Entry<String, Stack<StorageMenuItem<T>>> entry : menus
                        .entrySet()) {
                    JMenu subMenu = new JMenu(entry.getKey());
                    StorageMenuItem<T> sample = entry.getValue().peek();
                    if (sample != null) {
                        T t = sample.getItemStorage();
                        if (t != null) {
                            subMenu.setToolTipText(getTopMenuTipText(t));
                        }
                    }
                    fm.register(subMenu,
                            FontManager.FontType.monospace);
                    sortAndPopulate(subMenu, entry.getValue());
                    al.add(new ComparableMenu(subMenu));
                }
                Collections.sort(al);
                for (ComparableMenu mi : al) {
                    valueMenu.add(mi.getMenu());
                }
            }
        }
        getInnerComponent().invalidate();
        getMenuBar().invalidate();
        reset();
        getPanel().validate();
    }

    /**
     * Comparable menu.
     */
    final class ComparableMenu implements Comparable<ComparableMenu> {

        /**
         * @return Menu.
         */
        public JMenu getMenu() {
            return m;
        }

        /**
         * Menu.
         */
        private final JMenu m;

        /**
         * @param menu
         *            Menu.
         */
        public ComparableMenu(final JMenu menu) {
            m = menu;
        }

        @Override
        public int compareTo(final ComparableMenu o) {
            return m.getText().compareTo(o.m.getText());
        }

        @Override
        public int hashCode() {
            return m.getText().hashCode();
        }

        @Override
        public boolean equals(final Object o) {
            if (!(o instanceof Dropper<?>.ComparableMenu)) {
                return false;
            }
            @SuppressWarnings("unchecked")
            ComparableMenu c = (ComparableMenu) o;
            return c.m.getText().equals(this.m.getText());
        }
    }

    /**
     * Sort and populate.
     *
     * @param menu
     *            Menu to add sorted stack to.
     * @param stack
     *            Stack to sort.
     */
    private void sortAndPopulate(final JMenu menu,
            final Stack<StorageMenuItem<T>> stack) {
        ArrayList<StorageMenuItem<T>> al = new ArrayList<StorageMenuItem<T>>();
        while (!stack.isEmpty()) {
            al.add(stack.pop());
        }
        Collections.sort(al);
        for (StorageMenuItem<T> mi : al) {
            menu.add(mi);
        }
    }

    /**
     * Selector panel.
     */
    private TPanel selectorPnl;

    /**
     * @return Selector panel.
     */
    private TPanel getSelectorPnl() {
        if (this.selectorPnl == null) {
            this.selectorPnl = new TPanel();
            refresh();
            this.selectorPnl.add(getMenuBar());
        }
        return this.selectorPnl;
    }

    /**
     * Selection panel.
     */
    private TPanel selectionPnl;

    /**
     * @return Selection panel.
     */
    private TPanel getSelectionPnl() {
        if (this.selectionPnl == null) {
            this.selectionPnl = new TPanel();
            this.selectionPnl.add(getSelectionLbl());
        }
        return this.selectionPnl;
    }

    /**
     * Selection label.
     */
    private JLabel selectionLbl;

    /**
     * @return Selection label.
     */
    private JLabel getSelectionLbl() {
        if (this.selectionLbl == null) {
            this.selectionLbl = new JLabel();
        }
        return this.selectionLbl;
    }

    @Override
    protected final LayoutManager createLayout() {
        return new FlowLayout();
    }

    @Override
    protected final boolean isValid(final String valueToCheck) {
        return true;
    }
}
